package cn.edu.zisu.common.util;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;

import java.math.BigDecimal;
import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collector;
import java.util.stream.Collectors;

/**
 * @author zhang.wei@xinheyun.com
 * @date 2020-04-19 19:23
 */
public class ModelUtils {

    private ModelUtils () {}

    /**
     * @param <R> the type of input elements to the reduction operation
     * @param <A> the result type of the reduction operation
     * @throws NullPointerException when objs is null
     */
    public static <R, T, A> A properties(Collection<T> objs, Function<T, R> function, Collector<R, ?, A> collectors) {
        return objs.stream().map(function).collect(collectors);
    }

    public static <R, T> Set<R> properties(Collection<T> objs, Function<T, R> function) {
        if (CollectionUtils.isEmpty(objs)) {
            return Sets.newHashSet();
        }
        return properties(objs, function, Collectors.toSet());
    }

    public static <R, T> Map<R, T> convert2Map(Collection<T> objs, Function<T, R> function) {
        if (CollectionUtils.isEmpty(objs)) {
            return Maps.newHashMap();
        }
        return objs.stream().collect(Collectors.toMap(function, Function.identity(), (a, b) -> a));
    }

    public static <T, K, V> Map<K, V> convert2Map(Collection<T> objs, Function<T, K> keyMapper, Function<T, V> valueMapper) {
        if (CollectionUtils.isEmpty(objs)) {
            return Maps.newHashMap();
        }
        return objs.stream()
                .filter(e -> valueMapper.apply(e) != null && keyMapper.apply(e) != null)
                .collect(Collectors.toMap(keyMapper, valueMapper, (a, b) -> a));
    }

    public static <R, T> Map<R, List<T>> group2Map(Collection<T> objs, Function<T, R> function) {
        if (CollectionUtils.isEmpty(objs)) {
            return Maps.newHashMap();
        }
        return objs.stream().collect(Collectors.groupingBy(function));
    }

    public static <T, K, V> Map<K, Set<V>> group2Map(Collection<T> objs, Function<T, K> keyMapper, Function<T, V> valueMapper) {
        if (CollectionUtils.isEmpty(objs)) {
            return Maps.newHashMap();
        }
        return objs.stream()
                .filter(e -> keyMapper.apply(e) != null && valueMapper.apply(e) != null)
                .collect(Collectors.groupingBy(keyMapper,
                        Collectors.collectingAndThen(Collectors.toList(), e -> e.stream().map(valueMapper).collect(Collectors.toSet()))));
    }

    public static <R, T> List<R> map(Collection<T> objs, Function<T, R> function) {
        if (CollectionUtils.isEmpty(objs)) {
            return Lists.newArrayList();
        }
        return objs.stream()
                .filter(e -> function.apply(e) != null)
                .map(function)
                .collect(Collectors.toList());
    }

    public static <R, T> R firstMap(List<T> objs, Function<T, R> function) {
        return CollectionUtils.isNotEmpty(objs) ? function.apply(objs.get(0)) : null;
    }

    public static <T> T first(List<T> objs) {
        return firstOrDefault(objs, null);
    }

    public static <T> T firstOrDefault(List<T> objs, T defaultVal) {
        return CollectionUtils.isNotEmpty(objs) ? objs.get(0) : defaultVal;
    }

    public static <R, T> List<R> flat(Collection<T> objs, Function<T, List<R>> function) {
        if (CollectionUtils.isEmpty(objs)) {
            return Lists.newArrayList();
        }
        return objs.stream()
                .filter(e -> CollectionUtils.isNotEmpty(function.apply(e)))
                .flatMap(e -> function.apply(e).stream())
                .collect(Collectors.toList());
    }

    public static <T> BigDecimal reduce(Collection<T> objs, Function<T, BigDecimal> function) {
        if (CollectionUtils.isEmpty(objs)) {
            return BigDecimal.ZERO;
        }
        return objs.stream()
                .filter(e -> function.apply(e) != null)
                .map(function)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    public static <T> BigDecimal reduce(Collection<T> objs, Function<T, BigDecimal> function, Predicate<T> predicate) {
        if (CollectionUtils.isEmpty(objs)) {
            return BigDecimal.ZERO;
        }
        return objs.stream()
                .filter(e -> function.apply(e) != null && predicate.test(e))
                .map(function)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
    }

    @SafeVarargs
    public static <T> List<T> nonNullList(T... arr) {
        if (ArrayUtils.isEmpty(arr)) {
            return Lists.newArrayList();
        }
        List<T> results = Lists.newArrayList(arr);
        results.removeIf(Objects::isNull);
        return results;
    }
}
